home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / FILESYS.PY < prev    next >
Encoding:
Python Source  |  2000-06-02  |  11.3 KB  |  468 lines

  1. # -*- Mode: Python; tab-width: 4 -*-
  2. #    $Id: filesys.py,v 1.5 2000/06/02 14:22:48 brian Exp $
  3. #    Author: Sam Rushing <rushing@nightmare.com>
  4. #
  5. # Generic filesystem interface.
  6. #
  7.  
  8. # We want to provide a complete wrapper around any and all
  9. # filesystem operations.
  10.  
  11. # this class is really just for documentation,
  12. # identifying the API for a filesystem object.
  13.  
  14. # opening files for reading, and listing directories, should
  15. # return a producer.
  16.  
  17. class abstract_filesystem:
  18.     def __init__ (self):
  19.         pass
  20.  
  21.     def current_directory (self):
  22.         "Return a string representing the current directory."
  23.         pass
  24.  
  25.     def listdir (self, path, long=0):
  26.         """Return a listing of the directory at 'path' The empty string
  27.         indicates the current directory.  If 'long' is set, instead
  28.         return a list of (name, stat_info) tuples
  29.         """
  30.         pass
  31.  
  32.     def open (self, path, mode):
  33.         "Return an open file object"
  34.         pass
  35.  
  36.     def stat (self, path):
  37.         "Return the equivalent of os.stat() on the given path."
  38.         pass
  39.  
  40.     def isdir (self, path):
  41.         "Does the path represent a directory?"
  42.         pass
  43.  
  44.     def isfile (self, path):
  45.         "Does the path represent a plain file?"
  46.         pass
  47.  
  48.     def cwd (self, path):
  49.         "Change the working directory."
  50.         pass
  51.  
  52.     def cdup (self):
  53.         "Change to the parent of the current directory."
  54.         pass
  55.  
  56.  
  57.     def longify (self, path):
  58.         """Return a 'long' representation of the filename
  59.         [for the output of the LIST command]"""
  60.         pass
  61.  
  62. # standard wrapper around a unix-like filesystem, with a 'false root'
  63. # capability.
  64.  
  65. # security considerations: can symbolic links be used to 'escape' the
  66. # root?  should we allow it?  if not, then we could scan the
  67. # filesystem on startup, but that would not help if they were added
  68. # later.  We will probably need to check for symlinks in the cwd method.
  69.  
  70. # what to do if wd is an invalid directory?
  71.  
  72. import os
  73. import stat
  74.  
  75. import string
  76.  
  77. def safe_stat (path):
  78.     try:
  79.         return (path, os.stat (path))
  80.     except:
  81.         return None
  82.  
  83. import regex
  84. import regsub
  85. import glob
  86.  
  87. class os_filesystem:
  88.     path_module = os.path
  89.  
  90.     # set this to zero if you want to disable pathname globbing.
  91.     # [we currently don't glob, anyway]
  92.     do_globbing = 1
  93.  
  94.     def __init__ (self, root, wd='/'):
  95.         self.root = root
  96.         self.wd = wd
  97.  
  98.     def current_directory (self):
  99.         return self.wd
  100.  
  101.     def isfile (self, path):
  102.         p = self.normalize (self.path_module.join (self.wd, path))
  103.         return self.path_module.isfile (self.translate(p))
  104.  
  105.     def isdir (self, path):
  106.         p = self.normalize (self.path_module.join (self.wd, path))
  107.         return self.path_module.isdir (self.translate(p))
  108.  
  109.     def cwd (self, path):
  110.         p = self.normalize (self.path_module.join (self.wd, path))
  111.         translated_path = self.translate(p)
  112.         if not self.path_module.isdir (translated_path):
  113.             return 0
  114.         else:
  115.             old_dir = os.getcwd()
  116.             # temporarily change to that directory, in order
  117.             # to see if we have permission to do so.
  118.             try:
  119.                 can = 0
  120.                 try:
  121.                     os.chdir (translated_path)
  122.                     can = 1
  123.                     self.wd = p
  124.                 except:
  125.                     pass
  126.             finally:
  127.                 if can:
  128.                     os.chdir (old_dir)
  129.             return can
  130.  
  131.     def cdup (self):
  132.         return self.cwd ('..')
  133.  
  134.     def listdir (self, path, long=0):
  135.         p = self.translate (path)
  136.         # I think we should glob, but limit it to the current
  137.         # directory only.
  138.         ld = os.listdir (p)
  139.         if not long:
  140.             return list_producer (ld, 0, None)
  141.         else:
  142.             old_dir = os.getcwd()
  143.             try:
  144.                 os.chdir (p)
  145.                 # if os.stat fails we ignore that file.
  146.                 result = filter (None, map (safe_stat, ld))
  147.             finally:
  148.                 os.chdir (old_dir)
  149.             return list_producer (result, 1, self.longify)
  150.  
  151.     # TODO: implement a cache w/timeout for stat()
  152.     def stat (self, path):
  153.         p = self.translate (path)
  154.         return os.stat (p)
  155.  
  156.     def open (self, path, mode):
  157.         p = self.translate (path)
  158.         return open (p, mode)
  159.  
  160.     def unlink (self, path):
  161.         p = self.translate (path)
  162.         return os.unlink (p)
  163.  
  164.     def mkdir (self, path):
  165.         p = self.translate (path)
  166.         return os.mkdir (p)
  167.  
  168.     def rmdir (self, path):
  169.         p = self.translate (path)
  170.         return os.rmdir (p)
  171.  
  172.     # utility methods
  173.     def normalize (self, path):
  174.         # watch for the ever-sneaky '/+' path element
  175.         path = regsub.gsub ('/+', '/', path)
  176.         p = self.path_module.normpath (path)
  177.         # remove 'dangling' cdup's.
  178.         if len(p) > 2 and p[:3] == '/..':
  179.             p = '/'
  180.         return p
  181.  
  182.     def translate (self, path):
  183.         # we need to join together three separate
  184.         # path components, and do it safely.
  185.         # <real_root>/<current_directory>/<path>
  186.         # use the operating system's path separator.
  187.         path = string.join (string.split (path, '/'), os.sep)
  188.         p = self.normalize (self.path_module.join (self.wd, path))
  189.         p = self.normalize (self.path_module.join (self.root, p[1:]))
  190.         return p
  191.  
  192.     def longify (self, (path, stat_info)):
  193.         return unix_longify (path, stat_info)
  194.  
  195.     def __repr__ (self):
  196.         return '<unix-style fs root:%s wd:%s>' % (
  197.             self.root,
  198.             self.wd
  199.             )
  200.  
  201. if os.name == 'posix':
  202.  
  203.     class unix_filesystem (os_filesystem):
  204.         pass
  205.  
  206.     class schizophrenic_unix_filesystem (os_filesystem):
  207.         PROCESS_UID        = os.getuid()
  208.         PROCESS_EUID    = os.geteuid()
  209.         PROCESS_GID        = os.getgid()
  210.         PROCESS_EGID    = os.getegid()
  211.  
  212.         def __init__ (self, root, wd='/', persona=(None, None)):
  213.             os_filesystem.__init__ (self, root, wd)
  214.             self.persona = persona
  215.  
  216.         def become_persona (self):
  217.             if self.persona is not (None, None):
  218.                 uid, gid = self.persona
  219.                 # the order of these is important!
  220.                 os.setegid (gid)
  221.                 os.seteuid (uid)
  222.  
  223.         def become_nobody (self):
  224.             if self.persona is not (None, None):
  225.                 os.seteuid (self.PROCESS_UID)
  226.                 os.setegid (self.PROCESS_GID)
  227.  
  228.         # cwd, cdup, open, listdir
  229.         def cwd (self, path):
  230.             try:
  231.                 self.become_persona()
  232.                 return os_filesystem.cwd (self, path)
  233.             finally:
  234.                 self.become_nobody()
  235.  
  236.         def cdup (self, path):
  237.             try:
  238.                 self.become_persona()
  239.                 return os_filesystem.cdup (self)
  240.             finally:
  241.                 self.become_nobody()
  242.  
  243.         def open (self, filename, mode):
  244.             try:
  245.                 self.become_persona()
  246.                 return os_filesystem.open (self, filename, mode)
  247.             finally:
  248.                 self.become_nobody()
  249.  
  250.         def listdir (self, path, long=0):
  251.             try:
  252.                 self.become_persona()
  253.                 return os_filesystem.listdir (self, path, long)
  254.             finally:
  255.                 self.become_nobody()
  256.  
  257. # This hasn't been very reliable across different platforms.
  258. # maybe think about a separate 'directory server'.
  259. #
  260. #    import posixpath
  261. #    import fcntl
  262. #    import FCNTL
  263. #    import select
  264. #    import asyncore
  265. #
  266. #    # pipes /bin/ls for directory listings.
  267. #    class unix_filesystem (os_filesystem):
  268. #        pass
  269. #         path_module = posixpath
  270. #
  271. #         def listdir (self, path, long=0):
  272. #             p = self.translate (path)
  273. #             if not long:
  274. #                 return list_producer (os.listdir (p), 0, None)
  275. #             else:
  276. #                 command = '/bin/ls -l %s' % p
  277. #                 print 'opening pipe to "%s"' % command
  278. #                 fd = os.popen (command, 'rt')
  279. #                 return pipe_channel (fd)
  280. #
  281. #     # this is both a dispatcher, _and_ a producer
  282. #     class pipe_channel (asyncore.file_dispatcher):
  283. #         buffer_size = 4096
  284. #
  285. #         def __init__ (self, fd):
  286. #             asyncore.file_dispatcher.__init__ (self, fd)
  287. #             self.fd = fd
  288. #             self.done = 0
  289. #             self.data = ''
  290. #
  291. #         def handle_read (self):
  292. #             if len (self.data) < self.buffer_size:
  293. #                 self.data = self.data + self.fd.read (self.buffer_size)
  294. #             #print '%s.handle_read() => len(self.data) == %d' % (self, len(self.data))
  295. #
  296. #         def handle_expt (self):
  297. #             #print '%s.handle_expt()' % self
  298. #             self.done = 1
  299. #
  300. #         def ready (self):
  301. #             #print '%s.ready() => %d' % (self, len(self.data))
  302. #             return ((len (self.data) > 0) or self.done)
  303. #
  304. #         def more (self):
  305. #             if self.data:
  306. #                 r = self.data
  307. #                 self.data = ''
  308. #             elif self.done:
  309. #                 self.close()
  310. #                 self.downstream.finished()
  311. #                 r = ''
  312. #             else:
  313. #                 r = None
  314. #             #print '%s.more() => %s' % (self, (r and len(r)))
  315. #             return r
  316.  
  317. # For the 'real' root, we could obtain a list of drives, and then
  318. # use that.  Doesn't win32 provide such a 'real' filesystem?
  319. # [yes, I think something like this "\\.\c\windows"]
  320.  
  321. class msdos_filesystem (os_filesystem):
  322.     def longify (self, (path, stat_info)):
  323.         return msdos_longify (path, stat_info)
  324.  
  325. # A merged filesystem will let you plug other filesystems together.
  326. # We really need the equivalent of a 'mount' capability - this seems
  327. # to be the most general idea.  So you'd use a 'mount' method to place
  328. # another filesystem somewhere in the hierarchy.
  329.  
  330. # Note: this is most likely how I will handle ~user directories
  331. # with the http server.
  332.  
  333. class merged_filesystem:
  334.     def __init__ (self, *fsys):
  335.         pass
  336.  
  337. # this matches the output of NT's ftp server (when in
  338. # MSDOS mode) exactly.
  339.  
  340. def msdos_longify (file, stat_info):
  341.     if stat.S_ISDIR (stat_info[stat.ST_MODE]):
  342.         dir = '<DIR>'
  343.     else:
  344.         dir = '     '
  345.     date = msdos_date (stat_info[stat.ST_MTIME])
  346.     return '%s       %s %8d %s' % (
  347.         date,
  348.         dir,
  349.         stat_info[stat.ST_SIZE],
  350.         file
  351.         )
  352.  
  353. def msdos_date (t):
  354.     try:
  355.         info = time.gmtime (t)
  356.     except:
  357.         info = time.gmtime (0)
  358.     # year, month, day, hour, minute, second, ...
  359.     if info[3] > 11:
  360.         merid = 'PM'
  361.         info[3] = info[3] - 12
  362.     else:
  363.         merid = 'AM'
  364.     return '%02d-%02d-%02d  %02d:%02d%s' % (
  365.         info[1],
  366.         info[2],
  367.         info[0]%100,
  368.         info[3],
  369.         info[4],
  370.         merid
  371.         )
  372.  
  373. months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  374.           'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  375.  
  376. mode_table = {
  377.     '0':'---',
  378.     '1':'--x',
  379.     '2':'-w-',
  380.     '3':'-wx',
  381.     '4':'r--',
  382.     '5':'r-x',
  383.     '6':'rw-',
  384.     '7':'rwx'
  385.     }
  386.  
  387. import time
  388.  
  389. def unix_longify (file, stat_info):
  390.     # for now, only pay attention to the lower bits
  391.     mode = ('%o' % stat_info[stat.ST_MODE])[-3:]
  392.     mode = string.join (map (lambda x: mode_table[x], mode), '')
  393.     if stat.S_ISDIR (stat_info[stat.ST_MODE]):
  394.         dirchar = 'd'
  395.     else:
  396.         dirchar = '-'
  397.     date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])
  398.     return '%s%s %3d %-8s %-8s %8d %s %s' % (
  399.         dirchar,
  400.         mode,
  401.         stat_info[stat.ST_NLINK],
  402.         stat_info[stat.ST_UID],
  403.         stat_info[stat.ST_GID],
  404.         stat_info[stat.ST_SIZE],
  405.         date,
  406.         file
  407.         )
  408.         
  409. # Emulate the unix 'ls' command's date field.
  410. # it has two formats - if the date is more than 180
  411. # days in the past, then it's like this:
  412. # Oct 19  1995
  413. # otherwise, it looks like this:
  414. # Oct 19 17:33
  415.  
  416. def ls_date (now, t):
  417.     try:
  418.         info = time.gmtime (t)
  419.     except:
  420.         info = time.gmtime (0)
  421.     # 15,600,000 == 86,400 * 180
  422.     if (now - t) > 15600000:
  423.         return '%s %2d  %d' % (
  424.             months[info[1]-1],
  425.             info[2],
  426.             info[0]
  427.             )
  428.     else:
  429.         return '%s %2d %02d:%02d' % (
  430.             months[info[1]-1],
  431.             info[2],
  432.             info[3],
  433.             info[4]
  434.             )
  435.  
  436. # ===========================================================================
  437. # Producers
  438. # ===========================================================================
  439.  
  440. class list_producer:
  441.     def __init__ (self, file_list, long, longify):
  442.         self.file_list = file_list
  443.         self.long = long
  444.         self.longify = longify
  445.         self.done = 0
  446.  
  447.     def ready (self):
  448.         if len(self.file_list):
  449.             return 1
  450.         else:
  451.             if not self.done:
  452.                 self.done = 1
  453.             return 0
  454.         return (len(self.file_list) > 0)
  455.  
  456.     # this should do a pushd/popd
  457.     def more (self):
  458.         if not self.file_list:
  459.             return ''
  460.         else:
  461.             # do a few at a time
  462.             bunch = self.file_list[:50]
  463.             if self.long:
  464.                 bunch = map (self.longify, bunch)
  465.             self.file_list = self.file_list[50:]
  466.             return string.joinfields (bunch, '\r\n') + '\r\n'
  467.  
  468.